package com.yulian.yulianbackend.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.yulian.yulianbackend.common.ErrorCode;
import com.yulian.yulianbackend.exception.BusinessException;
import com.yulian.yulianbackend.service.UserService;
import com.yulian.yulianbackend.model.domain.User;
import com.yulian.yulianbackend.mapper.UserMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.yulian.yulianbackend.contant.UserConstant.ADMIN_ROLE;
import static com.yulian.yulianbackend.contant.UserConstant.USER_LOGIN_STATE;

/**
 * 用户服务实现类
 *
 * @author yulian174
 */
@Service
//lombok提供的注解
//可以使用log(记录日志，记录问题)
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    //注入mapper, 对数据库操作
    @Resource
    private UserMapper userMapper;
    /**
     * 盐值，混淆密码
     */
    private static final String SALT = "Yulian";


    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword) {
        // 1，校验
        //使用该方法可以判断多个参数是否为空，是否为null
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");//参数二为描述信息
        }
        //账户名的长度不小于4
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名不能小于四位");
        }
        //密码不小于八位
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码不小于八位");
        }
        //账户不能包含特殊字符
        String validPattern = "[`~!@#$名^&*()+=|{}':;',\\\\[\\\\].<>/?~! @#￥$.&*()-+|{}【】';:“w\n" +
                "?]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        //如果没有找到validPattern里面的字符(是特殊字符)
        if (matcher.find()) {
            return -1;
        }
        //判断两个字符串不能用==，要用equals进行比较
        //比较密码和校验密码
        if (!userPassword.equals(checkPassword)) {
            return -1;
        }


        //在数据库中查询是否有相同账户名的人
        //编写查询条件queryWrapper
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //查询账户名和我们的输入名相等
        queryWrapper.eq("userAccount", userAccount);
        //count()括号里是具体要从数据库查询哪些内容
        //this就是指向调用它的当前对象，即UserServiceImpl
        //将查询条件传递给方法
        long count = userMapper.selectCount(queryWrapper);
        //如果有人注册了
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户不能重复");

        }

        //加密
        //Spring密码加密类(永远不要想着对密码进行解密)
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());

        //向数据库插入数据
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setAvatarUrl("https://foruda.gitee.com/avatar/1714887959600492555/10396122_t-ly9z_1714887959.png!avatar200");
        user.setUsername(userAccount);
        //        user.setPlaneCode(planeCode);
        //使用save方法将对象保存到数据库中
        boolean saveResult = this.save(user);
        //如果保存失败(id未获取，即是null的话就退出)
        //索引1
        if (!saveResult) {
            return -1;
        }

        //最后返回用户id
        //索引1防止了后面返回long但是是null而报错
        return user.getId();
    }

    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1、校验
        //使用该方法可以判断多个参数是否为空，是否为null
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            //返回空信息，表示登录失败
            return null;
        }
        //账户名的长度不小于4
        if (userAccount.length() < 4) {
            return null;
        }
        //密码不小于八位
        if (userPassword.length() < 8) {
            return null;
        }
        //账户不能包含特殊字符
        String validPattern = "[`~!@#$名^&*()+=|{}':;',\\\\[\\\\].<>/?~! @#￥$.&*()-+|{}【】';:“w\n" +
                "?]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        //如果包含特殊字符
        if (matcher.find()) {
            return null;
        }

        //2、加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        //查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //编写查询条件：比较账号和密码
        queryWrapper.eq("userAccount", userAccount);
        queryWrapper.eq("userPassword", encryptPassword);
        //查询一条数据
        User user = userMapper.selectOne(queryWrapper);
        if (user == null) {
            //英文日志不容易出现乱码
            log.info("user login failed, userAccount cannot match userPassword");
            return null;
        }

        User safetyUser = getSafetyUser(user);
        //4、记录用户登录态(键值对) session
        // 后台存储已经登录的用户信息
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);

        //5、返回脱敏后的用户信息对象
        return safetyUser;
    }

    @Override
    public User getSafetyUser(User originUser) {
        //null可以使用等号连
        if (originUser == null) {
            return null;
        }
        //3、用户脱敏(安全账户,密码不返回)
        User safetyUser = new User();
        safetyUser.setId(originUser.getId());
        safetyUser.setUsername(originUser.getUsername());
        safetyUser.setUserAccount(originUser.getUserAccount());
        safetyUser.setAvatarUrl(originUser.getAvatarUrl());
        safetyUser.setGender(originUser.getGender());
        safetyUser.setPhone(originUser.getPhone());
        safetyUser.setEmail(originUser.getEmail());
        safetyUser.setSchool(originUser.getSchool());
        safetyUser.setCreateTime(originUser.getCreateTime());
        //设置返回权限
        safetyUser.setUserRole(originUser.getUserRole());
        safetyUser.setUserStatus(originUser.getUserStatus());//用户的状态
        safetyUser.setCreateTime(originUser.getCreateTime());
        safetyUser.setTags(originUser.getTags());
        return safetyUser;
    }

    @Override
    public int userLogout(HttpServletRequest request) {
        //移除记录用户登录态的属性
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return 1;
    }


    /**
     * 根据标签搜索用户
     *
     * @param tagNameList 用户要拥有的标签
     * @return int
     */
    @Override
    public List<User> searchUsersByTags(List<String> tagNameList) {
        //判断是否为空
        if (CollectionUtils.isEmpty(tagNameList)) {
            //如果为空，就抛用户输入异常
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
//        1、SQL查询
//        //创建查询
//        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//        //拼接 and 查询
//        // like '%Java%' and like '%Python%'
//        for (String tagName : tagNameList) {
//            // 每次循环遍历都拼接一个like，同时将上一个拼接好的内容赋值给自己
//            // 查找 user 表中的 tags 的名字与 tagName 对应
//            queryWrapper = queryWrapper.like("tags", tagName);
//        }
//        // 进行列表查询，并返回结果
//        List<User> userList = userMapper.selectList(queryWrapper);
//        // 遍历，对于每个user，进行脱敏并返回列表
//        return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());

//        2、内存查询
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //查询所有用户
        List<User> userList = userMapper.selectList(queryWrapper);
        //使用gson序列化库
        Gson gson = new Gson();
        //在内存中查询是否有匹配的标签
//        for (User user : userList) {
//            String tagStr = user.getTags();
//            // 左边是数据库中的JSON，右边是要解析的类型 (字符串集合)
//            //将JSON字符串反序列化成Java对象
//            //判断所有的 标签是否存在 使用集合更好
//            Set<String> tempTagNameSet = gson.fromJson(tagStr, new TypeToken<Set<String>>() {
//            }.getType());
//
//            //序列化 Java对象 => JSON
////            gson.toJson();
//
//            //遍历用户传入的tagNameList
//            for (String tagName : tagNameList) {
//                //如果集合中有任何一个tagName标签不存在
//                if (!tempTagNameSet.contains(tagName)) {
//                    return false;
//                }
//            }
//            //所有要求的标签都存在
//            return true;
//        }
//        return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());

        //简化以上内容的语法糖：
        //使用filter过滤掉所有不满足条件所有返回false的用户
        //`userList.parallelStream`：并行 / 并发流的缺点 ( 面试重点 )：
        /*
         * 使用同一个线程池，会出现一个问题
         * 如果查询数据库的代码也用了该线程池，最终查询数据库非常耗时，
         * 则该线程池就会被薅干净，就没有新的线程提供给其他的方法使用
         */
        /*
         * 尽量不要用parallelStream
         * 如果我们需要 查询用户/过滤用户 ， filter方法使用了CPU进行计算，它不涉及到查询数据库， 就可以使用
         * 如果使用了查询数据库的方法使用线程池，如果查询数据库一次花了十分钟，后面慢慢地线程池就会被耗干
         * 就没有多余的线程给其他方法使用了
         */
        return userList.stream().filter(user -> {
            String tagsStr = user.getTags();
            /*
             * Set<String> tempTagNameSet = gson.fromJson(tagStr, new TypeToken<Set<String>>() {}.getType());
             * tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>());
             *
             * gson.fromJson将JSON反序列化为一个Java对象
             *
             * Optional是Java8特性的类
             * ofNullable是一个链式调用
             * 首先，用ofNullable封装一个可能为空的对象 (tempTagNameSet)
             * 再用orElse给对象一个默认值，如果前面为空，就使用new HashSet<>()
             * 如果不为空，就使用Optional.ofNullable(tempTagNameSet)
             */
            //如果从数据库中取出的这条tags标签没有内容的话
//            if (StringUtils.isNotBlank(tagStr)) {
//                return false;
//            }
            // 左边是数据库中的JSON，右边是要解析的类型 (字符串集合)
            //将JSON字符串反序列化成Java对象
            //判断所有的 标签是否存在 使用集合更好
            Set<String> tempTagNameSet = gson.fromJson(tagsStr, new TypeToken<Set<String>>() {
            }.getType());

            /*
              这段代码使用了Java 8引入的Optional类来安全地处理可能为null的对象。
              它的作用是，如果tempTagNameSet是null，则将其替换为一个新的、空的HashSet实例，
              以避免后续对null对象的操作引发NullPointerException。

              具体来说：

              Optional.ofNullable(tempTagNameSet)：
              这行代码接受一个可能为null的参数tempTagNameSet，
              并将其封装到一个Optional对象中。
              如果tempTagNameSet为null，ofNullable方法不会抛出异常，而是返回一个包含null的Optional实例。

              .orElse(new HashSet<>())：
              紧接着，调用orElse方法指定了一个默认值。
              如果Optional对象包含一个非null的值（即tempTagNameSet非空），它将直接返回这个值。
              如果Optional对象包含null（即tempTagNameSet为null）
              则执行new HashSet<>()并返回这个新创建的、空的HashSet实例。

              所以，这段代码确保了无论tempTagNameSet原本是否为null，在执行完这行代码后，
              tempTagNameSet都会指向一个有效的、非空的HashSet实例，从而安全地继续进行后续的操作，
              比如添加元素、迭代等，而不用担心遇到NullPointerException。
              这是一种防御性编程的实践，用于提升代码的健壮性和安全性。
             */
            tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>());

            //序列化 Java对象 => JSON
//            gson.toJson();

            //遍历用户传入的tagNameList
            for (String tagName : tagNameList) {
                //如果集合中有任何一个tagName标签不存在
                if (!tempTagNameSet.contains(tagName)) {
                    return false;
                }
            }
            //所有要求的标签都存在
            return true;
        }).map(this::getSafetyUser).collect(Collectors.toList());
    }

    @Override
    // user是前端传来的要更新的用户信息
    // loginUser则是cookie中查询到的数据库的原来的用户信息
    public int updateUser(User user, User loginUser) {
        // 先查询要更新的用户
        // 根据 id 将用户信息查出来
        long userId = user.getId();
        if (userId <= 0) {
            // 请求参数有问题
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 如果是管理员，允许更新任意用户
        // 如果是用户自己，则只能更新自己
        // 如果当前登录的用户的 id 和 自己想改的自己的用户的 id 不一致
        if (!isAdmin(loginUser) && userId != loginUser.getId()) {
            // 抛出异常 ( 无权限 )
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        // 根据用户的 id 去查询 用户
        User oldUser = userMapper.selectById(userId);
        // 如果 oldUser 不存在的话
        if (oldUser == null) {
            // 请求的数据为空
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        // 根据 id 进行数据的更新
        // 返回值整数代表我们更新的用户条数 ( 更新了多少条用户信息 )
        return userMapper.updateById(user);
    }

    // 获取当前登录用户信息 ( cookie )
    @Override
    public User getLoginUser(HttpServletRequest request) {
        // 如果 cookie 为空的话
        if (request == null) {
            return null;
        }
        // 获取当前用户信息
        //从当前的HTTP请求的Session中获取名为 USER_LOGIN_STATE ( userLoginState ) 的属性值，并将其赋给userObj对象。
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        if (userObj == null) {
            // 如果为空就抛出异常 ( 没权限，未登录 )
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        // 进行类型转换
        return (User) userObj;
    }

    @Override
    public boolean isAdmin(HttpServletRequest request) {
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userObj;//可以不判断，对象允许为空(还是判断了是否为空)
        //如果不是管理员或者该对象为空
        return user == null || user.getUserRole() != ADMIN_ROLE;
    }


    /**
     * 方法重载
     *
     * @return boolean
     */
    @Override
    public boolean isAdmin(User loginUser) {
        return loginUser != null && loginUser.getUserRole() == ADMIN_ROLE;
    }

    @Override
    public User getCurrentUser(HttpServletRequest request) {
        if (request == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = (User) request.getSession().getAttribute(USER_LOGIN_STATE);
        if (user == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        return user;
    }
}




